import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get_instance/src/get_instance.dart';
import 'package:get/get_state_manager/get_state_manager.dart';

import '../network/cancel_token_group.dart';
import '../network/http.dart';

/// 对[GetBuilder]的重新封装
///
/// Created by wanggaowan on 2023/10/27 10:49
class KqGetBuilder<T extends KqGetXController> extends StatefulWidget {
  const KqGetBuilder({
    Key? key,
    this.init,
    this.global = true,
    required this.builder,
    this.autoRemove = true,
    this.initState,
    this.filter,
    this.tag,
    this.dispose,
    this.id,
    this.didChangeDependencies,
    this.didUpdateWidget,
    this.withTickerProvider = false,
  }) : super(key: key);

  final GetControllerBuilder<T> builder;

  /// 用于表示[KqGetXController]是否是全局对象
  final bool global;

  /// 用于[KqGetBuilderState.didUpdateWidget]判断数据是否发生变更,然后做相应数据更新
  final Object? id;

  /// 当[global] == true时，用于标记相同[KqGetXController]的不同实例
  final String? tag;

  /// 界面关闭时，是否移除controller并调用[KqGetXController.onClose]
  final bool autoRemove;

  /// 用于执行[KqGetXController.update]时，判断是否需要刷新，返回内容与上一次不一致时刷新
  final Object Function(T value)? filter;
  final void Function(KqGetBuilderState<T> state)? initState,
      dispose,
      didChangeDependencies;
  final void Function(KqGetBuilder oldWidget, KqGetBuilderState<T> state)?
      didUpdateWidget;

  /// 初始化[KqGetXController]，[global] == true且已通过[Get.put]设置了值时，此值可不传
  final T? init;
  final bool withTickerProvider;

  @override
  KqGetBuilderState<T> createState() => KqGetBuilderState<T>();
}

class KqGetBuilderState<T extends KqGetXController>
    extends State<KqGetBuilder<T>>
    with GetStateUpdaterMixin
    implements TickerProvider {
  T? controller;
  VoidCallback? _remove;
  Object? _filter;
  Set<Ticker>? _tickers;
  ValueListenable<bool>? _tickerModeNotifier;

  @override
  void initState() {
    super.initState();
    widget.initState?.call(this);
    _controllerAttachState();
  }

  void _controllerAttachState() {
    var isRegistered = GetInstance().isRegistered<T>(tag: widget.tag);
    if (widget.global) {
      if (isRegistered) {
        controller = GetInstance().find<T>(tag: widget.tag);
        _initController();
      } else {
        controller = widget.init;
        _initController();
        GetInstance().put<T>(controller!, tag: widget.tag);
      }
    } else {
      controller = widget.init;
      _initController();
      controller?.onStart();
    }

    if (widget.filter != null) {
      _filter = widget.filter!(controller!);
    }

    _subscribeToController();
  }

  void _initController() {
    if (mounted) {
      controller?._context = context;
    }
    controller?._vsync = this;
  }

  /// Register to listen Controller's events.
  /// It gets a reference to the remove() callback, to delete the
  /// setState "link" from the Controller.
  void _subscribeToController() {
    _remove?.call();
    _remove = (widget.id == null)
        ? controller?.addListener(
            _filter != null ? _filterUpdate : getUpdate,
          )
        : controller?.addListenerId(
            widget.id,
            _filter != null ? _filterUpdate : getUpdate,
          );
  }

  void _filterUpdate() {
    var newFilter = widget.filter!(controller!);
    if (newFilter != _filter) {
      _filter = newFilter;
      getUpdate();
    }
  }

  @override
  void dispose() {
    widget.dispose?.call(this);
    _removeController(widget);
    if (widget.withTickerProvider) {
      _disposeTickers();
    }
    super.dispose();
  }

  void _disposeTickers() {
    assert(() {
      if (_tickers != null) {
        for (final Ticker ticker in _tickers!) {
          if (ticker.isActive) {
            throw FlutterError.fromParts(<DiagnosticsNode>[
              ErrorSummary('$this was disposed with an active Ticker.'),
              ErrorDescription(
                '$runtimeType created a Ticker via its TickerProviderStateMixin, but at the time '
                'dispose() was called on the mixin, that Ticker was still active. All Tickers must '
                'be disposed before calling super.dispose().',
              ),
              ErrorHint(
                'Tickers used by AnimationControllers '
                'should be disposed by calling dispose() on the AnimationController itself. '
                'Otherwise, the ticker will leak.',
              ),
              ticker.describeForError('The offending ticker was'),
            ]);
          }
        }
      }
      return true;
    }());
    _tickerModeNotifier?.removeListener(_updateTickers);
    _tickerModeNotifier = null;
  }

  void _removeController(KqGetBuilder widget) {
    controller?.onDetach();
    if (widget.autoRemove) {
      if (GetInstance().isRegistered<T>(tag: widget.tag)) {
        GetInstance().delete<T>(tag: widget.tag);
      } else {
        controller?.onDelete();
      }
    }

    controller?._context = null;
    controller?._vsync = null;
    _remove?.call();
    controller = null;
    _remove = null;
    _filter = null;
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    widget.didChangeDependencies?.call(this);
  }

  @override
  void didUpdateWidget(KqGetBuilder oldWidget) {
    super.didUpdateWidget(oldWidget as KqGetBuilder<T>);
    // to avoid conflicts when modifying a "grouped" id list.
    if (oldWidget.id != widget.id) {
      _removeController(oldWidget);
      _controllerAttachState();
    }
    widget.didUpdateWidget?.call(oldWidget, this);
  }

  @override
  Widget build(BuildContext context) {
    return widget.builder(controller!);
  }

  @override
  Ticker createTicker(TickerCallback onTick) {
    if (_tickerModeNotifier == null) {
      // Setup TickerMode notifier before we vend the first ticker.
      _updateTickerModeNotifier();
    }
    assert(_tickerModeNotifier != null);
    _tickers ??= <_WidgetTicker>{};
    final _WidgetTicker result = _WidgetTicker(onTick, this,
        debugLabel: kDebugMode ? 'created by ${describeIdentity(this)}' : null)
      ..muted = !_tickerModeNotifier!.value;
    _tickers!.add(result);
    return result;
  }

  void _removeTicker(_WidgetTicker ticker) {
    assert(_tickers != null);
    assert(_tickers!.contains(ticker));
    _tickers!.remove(ticker);
  }

  @override
  void activate() {
    super.activate();
    if (!widget.withTickerProvider) {
      return;
    }
    // We may have a new TickerMode ancestor, get its Notifier.
    _updateTickerModeNotifier();
    _updateTickers();
  }

  void _updateTickers() {
    if (_tickers != null) {
      final bool muted = !_tickerModeNotifier!.value;
      for (final Ticker ticker in _tickers!) {
        ticker.muted = muted;
      }
    }
  }

  void _updateTickerModeNotifier() {
    final ValueListenable<bool> newNotifier = TickerMode.getNotifier(context);
    if (newNotifier == _tickerModeNotifier) {
      return;
    }
    _tickerModeNotifier?.removeListener(_updateTickers);
    newNotifier.addListener(_updateTickers);
    _tickerModeNotifier = newNotifier;
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    if (!widget.withTickerProvider) {
      return;
    }

    properties.add(DiagnosticsProperty<Set<Ticker>>(
      'tickers',
      _tickers,
      description: _tickers != null
          ? 'tracking ${_tickers!.length} ticker${_tickers!.length == 1 ? "" : "s"}'
          : null,
      defaultValue: null,
    ));
  }
}

abstract class KqGetXController extends GetxController {
  String get tag => runtimeType.toString();

  BuildContext? _context;

  TickerProvider? _vsync;

  /// 界面上下文
  ///
  /// 返回值为null表示[KqGetXController]关联的界面未挂载
  BuildContext? get context => _context;

  /// 仅当组件由[KqGetBuilder]包裹且[KqGetBuilder.withTickerProvider]为true时赋值。
  /// 调用此值前，需要判断是否挂载，[context] != null表示界面已挂载
  TickerProvider get vsync => _vsync!;

  /// 创建[CancelToken],用于界面退出时关闭相关接口
  CancelToken createCancelToken() {
    return CancelTokenEx.get(name: tag);
  }

  /// 与界面脱离，在[onClose]之前调用
  void onDetach() {}

  /// 界面关闭时调用，但是需要[KqGetBuilder.autoRemove] == true时才会主动调用，否则需要手动调用此方法
  @override
  void onClose() {
    /// 关闭弹窗仅仅为了防止接口请求被取消时，通过接口调用的弹窗无法关闭
    EasyLoading.dismiss();
    Http.closeByGroup(tag);
    super.onClose();
  }
}

class _WidgetTicker extends Ticker {
  _WidgetTicker(super.onTick, this._creator, {super.debugLabel});

  final KqGetBuilderState _creator;

  @override
  void dispose() {
    _creator._removeTicker(this);
    super.dispose();
  }
}
